package com.tanx.cqrs.infrastructure.spring.eventsourcing.snapshot;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanx.cqrs.eventsourcing.snapshot.Snapshot;
import com.tanx.cqrs.eventsourcing.snapshot.SnapshotBuildStrategy;
import com.tanx.cqrs.eventsourcing.snapshot.SnapshotRepository;
import lombok.Data;
import org.springframework.beans.factory.DisposableBean;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 根据事件数量创建快照
 */
public class CountSnapshotBuildStrategyImpl<T> implements SnapshotBuildStrategy<T>, DisposableBean {

    private SnapshotRepository<T> snapshotRepository;
    private ExecutorService executor = Executors.newCachedThreadPool();
    private int limit;

    public CountSnapshotBuildStrategyImpl(int limit, SnapshotRepository<T> snapshotRepository) {
        this.limit = limit;
        this.snapshotRepository = snapshotRepository;
    }

    @Override
    public boolean needCreate(T aggregate, List eventList) {
        return eventList.size() > limit;
    }

    @Override
    public void createSnap(T aggregate, Object id) {
        executor.execute(() -> snapshotRepository.save(new DefaultSnapshot(aggregate, id)));
    }

    @Override
    public SnapshotRepository<T> getSnapshotRepository() {
        return snapshotRepository;
    }

    @Override
    public void destroy() throws Exception {
        executor.shutdown();
    }

    @Data
    private class DefaultSnapshot implements Snapshot {
        private T aggregate;
        private String className;
        private Date createDateTime;
        private String content;
        private Object id;

        public DefaultSnapshot(T aggregate, Object id) {
            this.aggregate = aggregate;
            this.className = aggregate.getClass().getName();
            this.id = id;
            this.createDateTime = new Date();
            this.content = (String) toSnapshotContent(aggregate, id);
        }

        @Override
        public Object toSnapshotContent(Object aggregate, Object id) {
            ObjectMapper mapper = new ObjectMapper();
            try {
                return mapper.writeValueAsString(aggregate);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public Object toAggregate() {
            ObjectMapper mapper = new ObjectMapper();
            try {
                return mapper.readValue(content, Class.forName(className));
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
